/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.command.util;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Locale;

import jp.aist.gtrc.plus.reserve.QbaseReserveManager;
import jp.aist.gtrc.plus.reserve.ReserveId;
import jp.aist.gtrc.plus.scheduler.sys.SchedulerException;
import jp.aist.gtrc.plus.scheduler.util.CmdUtil;
import jp.aist.gtrc.plus.scheduler.util.TimeUtil;

public final class SGEAccount {

	private static final String QACCT_TIME_FORMAT = "%1$tY%1$tm%1$td%1$tH%1$tM.%1$tS";
	public static final String QACCT_DATE_FORMAT = "%1$tY-%1$tm-%1$td";

	private static class SGEAccountLine {
		private final String qName;
		private final String owner;
		private final int slots;
		private final long duration;
		private final String node;
		private static int qNameIndex = -1;

		private static synchronized void setQnameIndex(int index) {
			qNameIndex = index;
		}

		private String makeQueueName(String qName) {

			if (qName.endsWith(QbaseReserveManager.POSTFIX_PREQ)) {
				return qName.substring(0,
						qName.length() - QbaseReserveManager.POSTFIX_PREQ.length());
			} else if (qName.endsWith(QbaseReserveManager.POSTFIX_POSTQ)) {
				return qName.substring(0,
						qName.length() - QbaseReserveManager.POSTFIX_POSTQ.length());
			}
			return qName;

		}

		/*
		 *	$ qacct -q -o -h -slots -b 200611010000 -e 200611200000
		 	HOST              CLUSTER QUEUE OWNER    SLOTS    WALLCLOCK         UTIME         STIME           CPU             MEMORY                 IO                IOW
		 	==============================================================================================================================================================
		 	compute-0-1.local R40           carla        1          612             0             0             0              0.000              0.000              0.000
		 	compute-0-1.local R42           carla        1         1192             0             0             0              0.009              0.000              0.000
		 	compute-0-1.local R43           carla        1         1719             0             0             0              0.000              0.000              0.000
		 	compute-0-1.local R46           carla        1           86             0             1             2              0.712              0.000              0.000
		 	compute-0-1.local R53           demo         1          415             0             0             0              0.000              0.000              0.000
		 	compute-0-1.local R53post       demo         1            2             0             0             0              0.000              0.000              0.000
		 	compute-0-1.local R53pre        demo         1            3             0             0             0              0.000              0.000              0.000
		 	compute-0-12.localall.q         ohkubo-k     1           11             0             0             0              0.000              0.000              0.000
		 	                 ^ without space sometime!
		 */
		SGEAccountLine(String line) throws SchedulerException {

			String[] args = line.split("\\s+");
			if ((args.length < 10) && (qNameIndex < 0)) {
				throw new SchedulerException("Invalid line, ignore");
			}

			int index;
			if (args.length == 11) {
				node = args[0];
				qName = makeQueueName(args[1]);
				index = 2;
			} else {
				/*
				 * Without space between node and qName
				 */
				node = args[0].substring(0, qNameIndex);
				qName = args[0].substring(qNameIndex);
				index = 1;
			}
			owner = args[index++];
			slots = Integer.parseInt(args[index++]);
			duration = TimeUtil.secToMilli(Integer.parseInt(args[index++]));

		}

		public String toString() {

			return String.format("%s:%s:%d:%d:%s", qName, owner, slots, duration, node);

		}

	}

	public enum SGEAccountKey {
		queue, node, owner, startDate;
	}

	public static class SGEAccountData {
		private int count;
		private long duration;
		private int slots;
		private double hourNode;

		protected SGEAccountData(SGEAccountLine line) {

			this.count = 0;
			this.duration = 0;
			this.slots = 0;
			addNewLine(line);

		}

		protected SGEAccountData(long duration, int nodeNum) {

			this.count = 1;
			this.duration = duration;
			this.hourNode = TimeUtil.milliToHour(duration) * nodeNum;

		}

		protected final void addNewLine(SGEAccountLine line) {

			this.count++;
			this.duration += line.duration;
			this.slots += line.slots;
			this.hourNode += TimeUtil.milliToHour(line.duration);

		}

		public long getDuration() {
			return this.duration;
		}

		public int getCount() {
			return count;
		}

		public double getHourNode() {
			return hourNode;
		}

	}

	private final SGEAccountKey keyType;
	private final HashMap<String, SGEAccountData> normMap;
	private final HashMap<String, SGEAccountData> rsvMap;

	private SGEAccount(SGEAccountKey keyType) {

		this.keyType = keyType;
		normMap = new HashMap<String, SGEAccountData>();
		rsvMap = new HashMap<String, SGEAccountData>();

	}

	private String[] qacctCmd(Calendar start, Calendar end) {

		ArrayList<String> args = new ArrayList<String>();
		args.add("qacct");
		args.add("-q");
		args.add("-o");
		args.add("-h");
		args.add("-slots");
		if (start != null) {
			String startTime = String.format(Locale.US, QACCT_TIME_FORMAT, start);
			args.add("-b");
			args.add(startTime);
		}
		if (end != null) {
			String endTime = String.format(Locale.US, QACCT_TIME_FORMAT, end);
			args.add("-e");
			args.add(endTime);
		}

		return args.toArray(new String[args.size()]);

	}

	private LinkedList<String> runQacct(String[] cmd) throws SchedulerException {

		LinkedList<String> stdout = CmdUtil.runCommand(cmd);
		if (stdout.size() >= 2) {
			// remove header lines
			String header = stdout.removeFirst();
			SGEAccountLine.setQnameIndex(header.indexOf("CLUSTER QUEUE"));
			stdout.removeFirst();
		}
		return stdout;

	}

	private void checkSGEAccount(Calendar start, Calendar end, SGEAccountKey keyType) throws SchedulerException {

		LinkedList<String> stdout = runQacct(qacctCmd(start, end));
		for (String txt : stdout) {
			SGEAccountLine line;
			try {
				line = new SGEAccountLine(txt);
			} catch (SchedulerException e) {
				// ignore invalid line
				continue;
			}

			addLineToMap(keyType, line);
		}

	}

	private void checkSGEAccountByDate(Calendar start, Calendar end)
		throws SchedulerException {

		Calendar s = (Calendar)start.clone();
		Calendar e = (Calendar)start.clone();
		e.add(Calendar.DAY_OF_YEAR, 1);
		TimeUtil.truncateDate(e);

		String[] cmds = new String[] { "qacct", "-b", "START_TIME", "-e", "END_TIME", "-h" };
		HashMap<String, SGEAccountData> map = getTotalUsageMap();
		String startTime = String.format(Locale.US, QACCT_TIME_FORMAT, s), endTime;

		while (true) {
			endTime = String.format(Locale.US, QACCT_TIME_FORMAT, e);
			cmds[2] = startTime;
			cmds[4] = endTime;
			LinkedList<String> stdout = runQacct(cmds);
			int nodeNum = stdout.size();
			if (nodeNum > 0) {
				long duration = 0;
				for (int i = 0; i < nodeNum; i++) {
					String[] line = stdout.get(i).split("\\s+");
					if (line.length == 8) {
						duration += TimeUtil.secToMilli(Integer.parseInt(line[1]));
					}
				}
				String key = String.format(Locale.US, QACCT_DATE_FORMAT, s);
				SGEAccountData data = new SGEAccountData(duration, nodeNum);
				map.put(key, data);
			}
			if (e.after(end)) {
				break;
			}

			s.setTime(e.getTime());
			startTime = endTime;
			e.add(Calendar.DAY_OF_YEAR, 1);
		}
	}

	public SGEAccount(Calendar start, Calendar end, SGEAccountKey keyType) {

		this(keyType);

		try {
			if (keyType != SGEAccountKey.startDate) {
				checkSGEAccount(start, end, keyType);
			} else {
				checkSGEAccountByDate(start, end);
			}
		} catch (SchedulerException e) {
			// ignore
		}

	}

	private boolean isReserveUsed(SGEAccountLine line) {

		return (ReserveId.getInstance(line.qName) != null);

	}

	private void addLineToMap(SGEAccountKey keyType, SGEAccountLine line) {

		String keyName;
		switch (keyType) {
		case queue:
			keyName = line.qName;
			break;
		case node:
			keyName = line.node;
			break;
		case owner:
			keyName = line.owner;
			break;
		default:
			assert(false);
			return;
		}

		HashMap<String, SGEAccountData> map;
		if (isReserveUsed(line)) {
			map = rsvMap;
		} else {
			map = normMap;
		}

		SGEAccountData data = map.get(keyName);
		if (data == null) {
			data = new SGEAccountData(line);
			map.put(keyName, data);
		} else {
			data.addNewLine(line);
		}

	}

	private long getDuration(HashMap<String, SGEAccountData> map, String keyName) {

		SGEAccountData data = map.get(keyName);
		return (data != null) ? data.getDuration() : 0;

	}

	public long getNonRsvDuration(String keyName) {

		return getDuration(normMap, keyName);

	}

	public long getRsvDuration(String keyName) {

		return getDuration(rsvMap, keyName);

	}

	private double getHourNode(HashMap<String, SGEAccountData> map, String keyName) {

		SGEAccountData data = map.get(keyName);
		return (data != null) ? data.getHourNode() : 0.0;

	}

	public double getNonRsvHourNode(String keyName) {

		return getHourNode(normMap, keyName);

	}

	public double getRsvHourNode(String keyName) {

		return getHourNode(rsvMap, keyName);

	}

	public HashMap<String, SGEAccountData> getRsvUsageMap() {

		assert(keyType != SGEAccountKey.startDate);
		return rsvMap;

	}

	public HashMap<String, SGEAccountData> getNonRsvUsageMap() {

		assert(keyType != SGEAccountKey.startDate);
		return normMap;

	}

	public HashMap<String, SGEAccountData> getTotalUsageMap() {

		assert(keyType == SGEAccountKey.startDate);
		return normMap;

	}

}
